package com.ke.searchhouse.service.impl;

import com.ke.searchhouse.base.HouseSort;
import com.ke.searchhouse.base.HouseStatus;
import com.ke.searchhouse.base.LoginUserUtil;
import com.ke.searchhouse.base.Response;
import com.ke.searchhouse.dto.HouseDTO;
import com.ke.searchhouse.dto.HouseDetailDTO;
import com.ke.searchhouse.dto.HousePictureDTO;
import com.ke.searchhouse.entity.*;
import com.ke.searchhouse.form.*;
import com.ke.searchhouse.repository.*;
import com.ke.searchhouse.service.IHouseService;
import com.ke.searchhouse.service.ISearchService;
import com.ke.searchhouse.util.ServiceMultiResult;
import com.ke.searchhouse.util.ServiceResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.Predicate;
import javax.transaction.Transactional;
import javax.validation.Valid;
import java.util.*;

/**
 * @author  keke
 * @create  2018/10/15 0015 下午 9:28
 * @desc 房屋管理服务接口
 **/
@Service
public class HouseServiceImpl implements IHouseService{
    @Autowired
    private HouseRepository houseRepository;
    @Autowired
    private SubwayRepository subwayRepository;
    @Autowired
    private SubwayStationRepository subwayStationRepository;
    @Autowired
    private HouseDetailRepository houseDetailRepository;
    @Autowired
    private HousePrictureRepositroy housePrictureRepository;
    @Autowired
    private HouseTagRepository houseTagRepository;
    @Autowired
    private HouseSubscribeRepository subscribeRespository;
    @Autowired
    private ISearchService searchService;

    @Override
    @Transactional(rollbackOn = Exception.class)
    public ServiceResult<HouseDTO> save(HouseForm houseForm) {
        HouseDetail houseDetail = new HouseDetail();
        ServiceResult<HouseDTO> subwayValidationResult = wrapperDetailInfo(houseDetail, houseForm);
        if (null != subwayValidationResult) {
            return subwayValidationResult;
        }

        House house = new House();
        BeanUtils.copyProperties(houseForm, house);

        house.setAdminId(LoginUserUtil.getLoginUserId());
        house.setCreateTime(new Date());
        house.setLastUpdateTime(new Date());
        houseRepository.save(house);

        houseDetail.setHouseId(house.getId());
        houseDetail = houseDetailRepository.save(houseDetail);

        List<HousePicture> prictures = generatePrictures(houseForm, houseDetail.getHouseId());

        Iterable<HousePicture> housePictures =  housePrictureRepository.saveAll(prictures);

        HouseDTO houseDTO = new HouseDTO();
        BeanUtils.copyProperties(house, houseDTO);

        HouseDetailDTO houseDetailDTO = new HouseDetailDTO();
        BeanUtils.copyProperties(houseDetail, houseDetailDTO);

        houseDTO.setHouseDetail(houseDetailDTO);

        List<HousePictureDTO> housePictureDTOS = new ArrayList<>();
        housePictures.forEach(housePicture -> {
            HousePictureDTO housePictureDTO = new HousePictureDTO();
            BeanUtils.copyProperties(housePicture, housePictureDTO);
            housePictureDTOS.add(housePictureDTO);
        });

        houseDTO.setPictures(housePictureDTOS);

        List<String> houseTags = houseForm.getTags();
        if (houseTags != null && !houseTags.isEmpty()) {
            List<HouseTag> houseTagList = new ArrayList<>();
            houseTags.forEach(houseTag -> {
                houseTagList.add(new HouseTag(house.getId(), houseTag));
            });

            houseTagRepository.saveAll(houseTagList);
            houseDTO.setTags(houseTags);
        }

        return new ServiceResult<>(true, null, houseDTO);
    }

    @Override
    public ServiceMultiResult<HouseDTO> adminQuery(DatatableSearch searchBody) {
        List<HouseDTO> houseDTOS = new ArrayList<>();

        Sort sort = new Sort(Sort.Direction.fromString(searchBody.getDirection()), searchBody.getOrderBy());
        int page = searchBody.getStart() / searchBody.getLength();

        Pageable pageable = PageRequest.of(page, searchBody.getLength(), sort);

        Specification<House> specification = (root, query, cb) -> {
            Predicate predicate = cb.equal(root.get("adminId"), LoginUserUtil.getLoginUserId());
            predicate = cb.and(predicate, cb.notEqual(root.get("status"), HouseStatus.DELETED.getValue()));

            if (searchBody.getCity() != null) {
                predicate = cb.and(predicate, cb.equal(root.get("cityEnName"), searchBody.getCity()));
            }

            if (searchBody.getStatus() != null) {
                predicate = cb.and(predicate, cb.equal(root.get("status"), searchBody.getStatus()));
            }

            if (searchBody.getCreateTimeMin() != null) {
                predicate = cb.and(predicate, cb.greaterThanOrEqualTo(root.get("createTime"), searchBody.getCreateTimeMin()));
            }

            if (searchBody.getCreateTimeMax() != null) {
                predicate = cb.and(predicate, cb.lessThanOrEqualTo(root.get("createTime"), searchBody.getCreateTimeMax()));
            }

            if (searchBody.getTitle() != null) {
                predicate = cb.and(predicate, cb.like(root.get("title"), "%" + searchBody.getTitle() + "%"));
            }

            return predicate;
        };

        Page<House> houses = houseRepository.findAll(specification, pageable);
        houses.forEach(house -> {
            HouseDTO houseDTO = new HouseDTO();
            BeanUtils.copyProperties(house, houseDTO);
            houseDTOS.add(houseDTO);
        });

       return new ServiceMultiResult<>(houses.getTotalElements(), houseDTOS);
    }

    @Override
    public ServiceResult<HouseDTO> findCompleteOne(Long id) {

        House house = houseRepository.findById(id).orElse(null);

        if (house == null) {
            return ServiceResult.notFound();
        }

        HouseDTO houseDTO = new HouseDTO();
        BeanUtils.copyProperties(house, houseDTO);

        HouseDetail houseDetail = houseDetailRepository.findByHouseId(house.getId());
        HouseDetailDTO houseDetailDTO = new HouseDetailDTO();
        BeanUtils.copyProperties(houseDetail, houseDetailDTO);

        List<HouseTag> houseTagList = houseTagRepository.findAllByHouseId(house.getId());

        List<String> tagList = new ArrayList<>();
        houseTagList.forEach(houseTag -> {
            tagList.add(houseTag.getName());
        });

        List<HousePicture> housePictures = housePrictureRepository.findAllByHouseId(house.getId());
        List<HousePictureDTO> housePictureDTOS = new ArrayList<>();
        housePictures.forEach(housePicture -> {
            HousePictureDTO housePictureDTO = new HousePictureDTO();
            BeanUtils.copyProperties(housePicture, housePictureDTO);
            housePictureDTOS.add(housePictureDTO);
        });

        houseDTO.setTags(tagList);
        houseDTO.setPictures(housePictureDTOS);
        houseDTO.setHouseDetail(houseDetailDTO);

        if (LoginUserUtil.getLoginUserId() != null &&
                LoginUserUtil.getLoginUserId() > 0) { // 已登录用户
            HouseSubscribe subscribe = subscribeRespository.findByHouseIdAndUserId(house.getId(), LoginUserUtil.getLoginUserId());
            if (subscribe != null) {
                houseDTO.setSubscribeStatus(subscribe.getStatus());
            }
        }

        return ServiceResult.of(houseDTO);
    }

    @Override
    @Transactional
    public ServiceResult update(HouseForm houseForm) {
        House house = this.houseRepository.findById(houseForm.getId()).orElse(null);
        if (house == null) {
            return ServiceResult.notFound();
        }

        HouseDetail detail = this.houseDetailRepository.findByHouseId(house.getId());
        Long houseDetailId = detail.getId();

        if (detail == null) {
            return ServiceResult.notFound();
        }

        ServiceResult wrapperResult = wrapperDetailInfo(detail, houseForm);
        if (wrapperResult != null) {
            return wrapperResult;
        }
        detail.setId(houseDetailId);

        houseDetailRepository.save(detail);

        List<HousePicture> pictures = generatePictures(houseForm, houseForm.getId());
        housePrictureRepository.saveAll(pictures);

        if (houseForm.getCover() == null) {
            houseForm.setCover(house.getCover());
        }

        BeanUtils.copyProperties(houseForm, house);
        house.setLastUpdateTime(new Date());
        houseRepository.save(house);

        if (house.getStatus() == HouseStatus.PASSES.getValue()) {  // 上架保存到elasticsearch中
            searchService.index(house.getId());
        }

        return ServiceResult.success();
    }


    @Override
    public ServiceResult removePhoto(Long id) {
        HousePicture picture = housePrictureRepository.findById(id).orElse(null);
        if (picture == null) {
            return ServiceResult.notFound();
        }

        housePrictureRepository.deleteById(id);
        return ServiceResult.success();
    }

    @Override
    @Transactional
    public ServiceResult updateCover(Long coverId, Long targetId) {
        HousePicture cover = housePrictureRepository.findById(coverId).orElse(null);
        if (cover == null) {
            return ServiceResult.notFound();
        }

        houseRepository.updateCover(targetId, cover.getPath());
        return ServiceResult.success();
    }

    @Override
    @Transactional
    public ServiceResult addTag(Long houseId, String tag) {
        House house = houseRepository.findById(houseId).orElse(null);
        if (house == null) {
            return ServiceResult.notFound();
        }

        HouseTag houseTag = houseTagRepository.findByNameAndHouseId(tag, houseId);
        if (houseTag != null) {
            return new ServiceResult(false, "标签已存在");
        }

        houseTagRepository.save(new HouseTag(houseId, tag));
        return ServiceResult.success();
    }

    @Override
    @Transactional
    public ServiceResult removeTag(Long houseId, String tag) {
        House house = houseRepository.findById(houseId).orElse(null);
        if (house == null) {
            return ServiceResult.notFound();
        }

        HouseTag houseTag = houseTagRepository.findByNameAndHouseId(tag, houseId);
        if (houseTag == null) {
            return new ServiceResult(false, "标签不存在");
        }

        houseTagRepository.deleteById(houseTag.getId());
        return ServiceResult.success();
    }

    @Override
    @Transactional
    public ServiceResult updateStatus(Long id, int status) {
        House house = houseRepository.findById(id).orElse(null);
        if (house == null) {
            return ServiceResult.notFound();
        }

        if (house.getStatus() == status) {
            return new ServiceResult(false, "状态没有发生变化");
        }

        if (house.getStatus() == HouseStatus.RENTED.getValue()) {
            return new ServiceResult(false, "已出租的房源不允许修改状态");
        }

        if (house.getStatus() == HouseStatus.DELETED.getValue()) {
            return new ServiceResult(false, "已删除的资源不允许操作");
        }

        houseRepository.updateStatus(id, status);

        // 下架保存到elasticsearch中， 下架删除
        if (status == HouseStatus.PASSES.getValue()) {  // 上架保存到elasticsearch中
            searchService.index(id);
        } else {
            searchService.remove(id);
        }

        return ServiceResult.success();
    }

    @Override
    public ServiceMultiResult<HouseDTO> query(RentSearch rentSearch) {
        if (StringUtils.isNotBlank(rentSearch.getKeywords())) {
            ServiceMultiResult<Long> result = searchService.query(rentSearch);

            if (result.getTotal() == 0) {
                return new ServiceMultiResult<>(0, new ArrayList<>());
            }

            return new ServiceMultiResult<>(result.getTotal(), wrapperHouseResult(result.getResult()));
        }

        return simpleQuery(rentSearch);
    }

    @Override
    public ServiceMultiResult<HouseDTO> wholeMapQuery(MapSearch mapSearch) {
        ServiceMultiResult<Long> result = searchService.mapQuery(
                mapSearch.getCityEnName(), mapSearch.getOrderBy(),
                mapSearch.getOrderDirection(), mapSearch.getStart(), mapSearch.getSize());

        if (result.getTotal() == 0) {
            return new ServiceMultiResult<>(0, new ArrayList<>());
        }

        List<HouseDTO> houses = wrapperHouseResult(result.getResult());
        return new ServiceMultiResult<>(result.getTotal(), houses);
    }

    @Override
    public ServiceMultiResult<HouseDTO> boundMapQuery(MapSearch mapSearch) {
        ServiceMultiResult<Long> result = searchService.mapQuery(mapSearch);

        if (result.getTotal() == 0) {
            return new ServiceMultiResult<>(0, new ArrayList<>());
        }

        List<HouseDTO> houses = wrapperHouseResult(result.getResult());
        return new ServiceMultiResult<>(result.getTotal(), houses);
    }


    private ServiceMultiResult<HouseDTO> simpleQuery(RentSearch rentSearch) {
        Sort sort = HouseSort.generateSort(rentSearch.getOrderBy(), rentSearch.getOrderDirection());
        int page = rentSearch.getStart() / rentSearch.getSize();

        Pageable pageable =PageRequest.of(page, rentSearch.getSize(), sort);

        Specification<House> specification = ((root, criteriaQuery, criteriaBuilder) -> {
            Predicate predicate = criteriaBuilder.equal(root.get("status"), HouseStatus.PASSES.getValue());
            predicate = criteriaBuilder.and(predicate,
                    criteriaBuilder.equal(root.get("cityEnName"), rentSearch.getCityEnName()));

            if (HouseSort.DISTANCE_TO_SUBWAY_KEY.equals(rentSearch.getOrderBy())) {
                predicate = criteriaBuilder.and(predicate,
                        criteriaBuilder.gt(root.get(HouseSort.DISTANCE_TO_SUBWAY_KEY), -1));
            }

            return predicate;
        });

        Page<House> houses = houseRepository.findAll(specification, pageable);
        List<HouseDTO> houseDTOS = new ArrayList<>();
        List<Long> houseIds = new ArrayList<>();
        Map<Long, HouseDTO> houseDTOMap = new HashMap<>();

        houses.forEach(house -> {
            HouseDTO houseDTO = new HouseDTO();
            BeanUtils.copyProperties(house, houseDTO);
            houseDTOS.add(houseDTO);

            houseIds.add(house.getId());

            houseDTOMap.put(house.getId(), houseDTO);
        });

        wrapperHouseList(houseIds, houseDTOMap);

        return new ServiceMultiResult<>(houses.getTotalElements(), houseDTOS);
    }

    private List<HouseDTO> wrapperHouseResult(List<Long> houseIds) {
        List<HouseDTO> result = new ArrayList<>();

        Map<Long, HouseDTO> idToHouseMap = new HashMap<>();
        Iterable<House> houses = houseRepository.findAllById(houseIds);
        houses.forEach(house -> {
            HouseDTO houseDTO = new HouseDTO();
            BeanUtils.copyProperties(house, houseDTO);
            idToHouseMap.put(house.getId(), houseDTO);
        });

        wrapperHouseList(houseIds, idToHouseMap);

        // 矫正顺序
        for (Long houseId : houseIds) {
            result.add(idToHouseMap.get(houseId));
        }
        return result;
    }





    private List<HousePicture> generatePrictures(HouseForm houseForm, Long houseId) {
        List<HousePicture> housePictures = new ArrayList<>();
        if (houseForm.getPhotos() == null || houseForm.getPhotos().isEmpty()) {
            return housePictures;
        }

        houseForm.getPhotos().forEach(photoForm -> {
            HousePicture housePicture = new HousePicture();
            BeanUtils.copyProperties(photoForm, housePicture);
            housePicture.setHouseId(houseId);
            housePicture.setCdnPrefix("D:/upload/");
            housePictures.add(housePicture);
        });

        return housePictures;
    }



     private ServiceResult<HouseDTO> wrapperDetailInfo(HouseDetail houseDetail, HouseForm houseForm) {
          Subway subway = subwayRepository.findById(houseForm.getSubwayLineId()).orElse(null);
          if (subway == null) {
              return new ServiceResult<>(false, "Not valid subway");
          }

          SubwayStation subwayStation = subwayStationRepository.findById(houseForm.getSubwayStationId()).orElse(null);
          if (subwayStation == null || subway.getId() != subwayStation.getSubwayId()) {
              return new ServiceResult<>(false, "Not valid subwayStation");
          }

          BeanUtils.copyProperties(houseForm, houseDetail);
          houseDetail.setSubwayLineId(subway.getId());
          houseDetail.setSubwayLineName(subway.getName());

          houseDetail.setSubwayStationId(subwayStation.getId());
          houseDetail.setSubwayStationName(subwayStation.getName());

          return null;
     }

    /**
     * 渲染详细信息 及 标签
     * @param houseIds
     * @param idToHouseMap
     */
    private void wrapperHouseList(List<Long> houseIds, Map<Long, HouseDTO> idToHouseMap) {
        List<HouseDetail> details = houseDetailRepository.findAllByHouseIdIn(houseIds);
        details.forEach(houseDetail -> {
            HouseDTO houseDTO = idToHouseMap.get(houseDetail.getHouseId());
            HouseDetailDTO detailDTO = new HouseDetailDTO();
            BeanUtils.copyProperties(houseDetail, detailDTO);
            houseDTO.setHouseDetail(detailDTO);
        });

        List<HouseTag> houseTags = houseTagRepository.findAllByHouseIdIn(houseIds);
        houseTags.forEach(houseTag -> {
            HouseDTO houseDTO = idToHouseMap.get(houseTag.getHouseId());
            houseDTO.getTags().add(houseTag.getName());
        });
    }

    /**
     * 图片对象列表信息填充
     * @param form
     * @param houseId
     * @return
     */
    private List<HousePicture> generatePictures(HouseForm form, Long houseId) {
        List<HousePicture> pictures = new ArrayList<>();
        if (form.getPhotos() == null || form.getPhotos().isEmpty()) {
            return pictures;
        }

        for (PhotoForm photoForm : form.getPhotos()) {
            HousePicture picture = new HousePicture();
            picture.setHouseId(houseId);
            picture.setCdnPrefix("https://images-bucker.oss-cn-beijing.aliyuncs.com/");
            picture.setPath(photoForm.getPath());
            picture.setWidth(photoForm.getWidth());
            picture.setHeight(photoForm.getHeight());
            pictures.add(picture);
        }
        return pictures;
    }

}
